﻿using System;
using System.Collections.Generic;
using System.Text;

using TwinHead.Frameworks.Morph.Stream;

namespace TwinHead.Frameworks.Morph.Tokens
{
    /// <summary>
    /// 문자열 토큰을 생성하는 클래스
    /// </summary>
    public class Tokenizer : TokenStream, IDisposable
    {
        /// <summary>
        /// 파일이나, 문자열을 읽는 Reader
        /// </summary>
        protected IReader reader;
        /// <summary>
        /// 컬럼
        /// </summary>
        protected int col = 1;
        /// <summary>
        /// 줄(라인)
        /// </summary>
        protected int line = 1;
        /// <summary>
        /// 토큰을 생성하는 규칙을 구분
        /// </summary>
        protected NGramMethod nGramMethod = NGramMethod.NONE;
        /// <summary>
        /// 토큰목록
        /// </summary>
        protected List<IToken> tokenCollection = new List<IToken>();
        /// <summary>
        /// 단어목록
        /// </summary>
        protected List<string> derivedWordAllSet = new List<string>();

        /// <summary>
        /// Ireader 객체가 외부에서 전달된 객체인가?
        /// </summary>
        protected bool isExternalReader = false;

        private Tokenizer()
        {
            Generate();
        }

        /// <summary>
        /// 문자열 토큰을 생성하는 객체를 생성합니다.
        /// </summary>
        /// <param name="file">문자열을 읽어올 파일명</param>
        /// <param name="encoding">파일을 읽을때 사용하는 인코딩</param>
        public Tokenizer( string file, Encoding encoding )
            : this( file, encoding, NGramMethod.NONE )
        {
        }

        /// <summary>
        /// 문자열 토큰을 생성하는 객체를 생성합니다.
        /// </summary>
        /// <param name="file">문자열을 읽어올 파일명</param>
        /// <param name="encoding">파일을 읽을때 사용하는 인코딩</param>
        /// <param name="nGramMethod">토큰을 생성하는 방식</param>
        public Tokenizer( string file, Encoding encoding, NGramMethod nGramMethod )
        {
            this.reader = new FileReader( file, encoding );
            this.nGramMethod = nGramMethod;
            //Generate();
        }

        /// <summary>
        /// 문자열 토큰을 생성하는 객체를 생성합니다.
        /// </summary>
        /// <param name="wordStrings">토큰생성대상이 되는 문자열</param>
        public Tokenizer( string wordStrings )
            : this( wordStrings, NGramMethod.NONE )
        {
        }

        /// <summary>
        /// 문자열 토큰을 생성하는 객체를 생성합니다.
        /// </summary>
        /// <param name="wordStrings">토큰생성대상이 되는 문자열</param>
        /// <param name="nGramMethod">토큰을 생성하는 방식</param>
        public Tokenizer( string wordStrings, NGramMethod nGramMethod )
        {
            this.reader = new StringReader( wordStrings );
            this.nGramMethod = nGramMethod;
            //Generate();
        }

        /// <summary>
        /// 문자열 토큰을 생성하는 객체를 생성합니다.
        /// </summary>
        /// <param name="reader">문자열을 획득하는 인터페이스를 제공하는 리더</param>
        public Tokenizer( IReader reader )
            : this( reader, NGramMethod.NONE )
        {
        }

        /// <summary>
        /// 문자열 토큰을 생성하는 객체를 생성합니다.
        /// </summary>
        /// <param name="reader">문자열을 획득하는 인터페이스를 제공하는 리더</param>
        /// <param name="nGramMethod">토큰을 생성하는 방식</param>
        public Tokenizer( IReader reader, NGramMethod nGramMethod )
        {
            this.reader = reader;
            this.nGramMethod = nGramMethod;

            isExternalReader = true;

            //Generate();
        }

        /// <summary>
        /// 다시 생성합니다.
        /// </summary>
        /// <param name="nGramMethod"></param>
        public void ReGenerate( NGramMethod nGramMethod )
        {
            this.nGramMethod = nGramMethod;
            derivedWordAllSet.Clear();
            tokenCollection.Clear();
            Generate();
        }

        /// <summary>
        /// 다시 생성합니다.
        /// </summary>
        public void ReGenerate()
        {
            derivedWordAllSet.Clear();
            tokenCollection.Clear();
            Generate();
        }

        private void Generate()
        {
            IToken tok = null;

            if (reader == null) return;

            while ((tok = GetToken()) != null)
            {
                if (tok.Type == TokenType.EOF) break;
                tokenCollection.Add( tok );
                derivedWordAllSet.AddRange( tok.DerivedWordSet );
            }
        }

        /// <summary>
        /// 다음 토큰생성합니다.
        /// </summary>
        /// <returns></returns>
        protected virtual IToken GetToken()
        {
            try
            {
                char ch = '\0';
                int curCol = 1;
                while (!reader.Eos())
                {
                    ch = reader.GetNext();

                    if (Char.IsWhiteSpace( ch ) || Char.IsSeparator( ch ) || Char.IsPunctuation( ch ))// || Char.IsSymbol(ch))
                    {
                        ++col;
                        if (ch == '\n')
                        {
                            ++line;
                            col = 1;
                        }
                        continue;
                    }

                    reader.UnGet();
                    curCol = col;
                    return new Token( nGramMethod, TokenType.LetterString, ReadTerm(), line, curCol );
                }
            }
            catch
            {
                // 의도적으로 오류를 제어했다.
                // TODO: 향후 논란의 여지가 될 수 있는 부분.
            }
            finally
            {
            }
            return new Token( nGramMethod, TokenType.EOF, null, -1, -1 );
        }

        /// <summary>
        /// 공백을 기준으로 하나의 어절을 반환합니다.
        /// </summary>
        /// <returns></returns>
        protected virtual string ReadTerm()
        {
            char ch = '\0';
            StringBuilder sb = new StringBuilder();
            while (!reader.Eos() && !Char.IsWhiteSpace( ch = reader.GetNext() ))
            {
                ++col;
                if (ch == '\n')
                {
                    // 줄바꿈일경우 중지.(토근단위 생성되는 시점임)
                    ++line;
                    col = 1;
                    return sb.ToString();
                }

                if (Char.IsSeparator( ch ) || Char.IsPunctuation( ch ))// || Char.IsSymbol(ch))
                {
                    //IsSeparator, IsPunctuation 인경우 중지.(토근단위 생성되는 시점임)

                    //col=1;
                    return sb.ToString();
                }
                sb.Append( ch );
            }
            return sb.ToString();
        }

        /// <summary>
        /// 토큰들의 원본 문자열
        /// </summary>
        public override string SourceString
        {
            get
            {
                if (reader != null)
                    return reader.OriginalText;

                return string.Empty;
            }
        }

        /// <summary>
        /// 분해된 토큰의 개수를 반환합니다.
        /// </summary>
        public override int TokenCount
        {
            get
            {
                if (tokenCollection != null)
                    return tokenCollection.Count;

                return 0;
            }
        }

        /// <summary>
        /// 현재 스트림에 저장된 Token의 목록을 반환합니다.
        /// </summary>
        public override IList<IToken> Tokens
        {
            get
            {
                if (tokenCollection != null)
                    return tokenCollection;

                return new List<IToken>( 0 );
            }
        }

        /// <summary>
        /// 현재 스트림에 저장된 모든 토큰에서 분석된 단어목록을 반환합니다.
        /// </summary>
        public IList<string> AllDerivedWords
        {
            get
            {
                if (derivedWordAllSet != null)
                    return this.derivedWordAllSet;

                return new List<string>( 0 );
            }
        }

        /// <summary>
        /// 다음 토큰요소를 반환합니다.
        /// </summary>
        /// <returns>없을 경우 null 반환</returns>
        public override IToken Next()
        {
            if (tokenCollection == null) return null;

            if (tokenIndex < tokenCollection.Count - 1)
            {
                tokenIndex++;
                return tokenCollection[tokenIndex];
            }
            return null;
        }

        /// <summary>
        /// 이전 토큰요소를 반환합니다.
        /// </summary>
        /// <returns>없을 경우 null 반환</returns>
        public override IToken Prev()
        {
            if (tokenIndex > 0)
            {
                tokenIndex--;

                if (tokenCollection != null && tokenIndex < tokenCollection.Count - 1)
                    return tokenCollection[tokenIndex];
            }

            return null;
        }

        /// <summary>
        /// 현재 설정된 토큰요소를 반환합니다.
        /// </summary>
        /// <returns></returns>
        public override IToken Current()
        {
            if (tokenCollection != null && tokenIndex >= 0 && tokenIndex <= tokenCollection.Count - 1)
            {
                return tokenCollection[tokenIndex];
            }

            return null;
        }

        /// <summary>
        /// 제일 처음 토큰요소를 반환합니다.
        /// </summary>
        /// <returns></returns>
        public override IToken First()
        {
            if (tokenCollection == null) return null;

            return tokenCollection[0];
        }

        /// <summary>
        /// 제일 마지막 토큰요소를 반환합니다.
        /// </summary>
        /// <returns></returns>
        public override IToken Last()
        {
            if (tokenCollection == null) return null;

            return tokenCollection[tokenCollection.Count - 1];
        }

        /// <summary>
        /// 현재 적용중인 토큰 생성방법을 반환합니다.
        /// </summary>
        public NGramMethod CurrentNGramMethod
        {
            get { return this.nGramMethod; }
        }

        private bool isDisposed = false;
        /// <summary>
        /// 자원을 해제합니다.
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose( bool disposing )
        {
            if (isDisposed) return;
            if (disposing)
            {
                if (!isExternalReader)
                {
                    if (reader != null)
                    {
                        this.reader.Dispose();
                        this.reader = null;
                    }
                }

                // 관리자원 해제
                if (tokenCollection != null)
                {
                    tokenCollection.Clear();
                    tokenCollection = null;
                }
            }
            // 비관리 자원해제
            isDisposed = true;
        }

        /// <summary>
        /// 자원을 해제합니다.
        /// </summary>
        public void Dispose()
        {
            Dispose( true );
            GC.SuppressFinalize( this );
        }
    }
}
